<?php

namespace app\admin\controller;

use app\libs\database\Database as Databases;

use think\facade\Db;
use think\facade\Config;
use think\Request;

class Database extends Base
{
    /**
     * 备份配置
     */
    private $config;

    /**
     * 数据库备份初始化
     */
    public function initialize()
    {
        parent::initialize();

        // 读取备份配置
        $config = array(
            'file_path' => Config::get('path.db_file_path'),  // 文件路径
            'path' => Config::get('path.db_path'),  // 绝对路径
            'part' => Config::get('path.db_part'),  // 分卷大小 20M
            'compress' => Config::get('path.db_compress'),  // 0:不压缩 1:启用压缩
            'level' => Config::get('path.db_level'),  // 压缩级别, 1:普通 4:一般  9:最高
        );

        $this->config = $config;
    }

    /**
     * 数据库备份列表
     */
    public function export(Request $request)
    {
        if ($request->isAjax()) {
            //$list = Db::query('SHOW TABLES');
            $data = Db::query('SHOW TABLE STATUS');

            if ($data) {
                // 处理列表数据
                foreach ($data as &$item) {
                    $item['Data_length'] = format_bytes($item['Data_length']);
                }
            }

            $result = [
                'code' => '0',
                'msg' => '请求成功',
                'data' => $data,
                'count' => count($data),
            ];

            return json($result);
        }

        return view('database/export', [
            //'datas' => $list
        ]);
    }

    /**
     * 数据库还原列表
     */
    public function import(Request $request)
    {
        if ($request->isAjax()) {
            // 判断目录是否存在
            is_writeable($this->config['path']) || mkdir($this->config['path'], 0777, true);
            // 列出备份文件列表
            /*$path = realpath($this->config['path']);
            $flag = \FilesystemIterator::KEY_AS_FILENAME;
            $glob = new \FilesystemIterator($path,  $flag);

            $list = array();
            foreach ($glob as $name => $file) {
                if(preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql(?:\.gz)?$/', $name)){
                    $name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d');
                    $date = "{$name[0]}-{$name[1]}-{$name[2]}";
                    $time = "{$name[3]}:{$name[4]}:{$name[5]}";
                    $part = $name[6];

                    if(isset($list["{$date} {$time}"])){
                        $info = $list["{$date} {$time}"];
                        $info['part'] = max($info['part'], $part);
                        $info['size'] = $info['size'] + $file->getSize();
                    } else {
                        $info['part'] = $part;
                        $info['size'] = $file->getSize();
                    }
                    $extension        = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION));
                    $info['compress'] = ($extension === 'SQL') ? '-' : $extension;
                    $info['time']     = strtotime("{$date} {$time}");
                    $info['name']     = date('Ymd-His', $info['time']);

                    $info['mtime'] = "{$date} {$time}"; //备份数据库
                    $list["{$date} {$time}"] = $info;
                }
            }*/

            $pattern = "{*.sql,*.gz}";
            $fileList = glob($this->config['path'] . $pattern, GLOB_BRACE);
            $data = [];
            foreach ($fileList as $i => $file) {
                // 只读取文件
                if (is_file($file)) {
                    $size = filesize($file);
                    $filename = basename($file);
                    $filename_arr = explode('-', $filename);
                    $name = substr($filename, 0, strrpos($filename, '-'));
                    $extension = strtoupper(pathinfo($filename, PATHINFO_EXTENSION));
                    $search = [$name . '-', '.sql', '.gz'];
                    $replace = ['', '', ''];
                    $part = str_replace($search, $replace, $filename);
                    //$time = filemtime($file); // 上次修改时间
                    //$time = filectime($file); // 创建时间
                    $time = $filename_arr[1];
                    $data[] = [
                        'full_filepath' => asset($this->config['file_path'] . $filename),
                        'filepath' => $filename,
                        'name' => $name,
                        'part' => $part,
                        //'size' => $size,
                        'size' => format_bytes($size),
                        'compress' => ($extension === 'SQL') ? '-' : $extension,
                        'time' => $time,
                        'mtime' => date('Y-m-d H:i:s', strtotime($time))
                    ];
                }
            }

            $result = [
                'code' => '0',
                'msg' => '请求成功',
                'data' => $data,
                'count' => count($data),
            ];

            return json($result);
        }

        return view('database/import', [
            //'datas' => $list
        ]);
    }

    /**
     * 优化表
     * @param  String $tables 表名
     */
    public function optimize(Request $request, $tables = null)
    {
        $tables = $request->param('tables');
        if ($tables) {
            if (is_array($tables)) {
                $tables = implode('`,`', $tables);
                $list = Db::query("OPTIMIZE TABLE `{$tables}`");

                if ($request->isPost()) {
                    return json([
                        'code' => $list ? 1 : 0,
                        'status' => $list ? 'success' : 'error',
                        'info' => $list ? __('数据表优化完成！') : __('数据表优化出错请重试！'),
                        'referer' => (string)url('admin/' . $this->model . '/export')
                    ]);
                }
            } else {
                $list = Db::query("OPTIMIZE TABLE `{$tables}`");

                if ($request->isPost()) {
                    return json([
                        'code' => $list ? 1 : 0,
                        'status' => $list ? 'success' : 'error',
                        'info' => $list ? __("数据表'{$tables}'优化完成！") : __("数据表'{$tables}'优化出错请重试！"),
                        'referer' => (string)url('admin/' . $this->model . '/export')
                    ]);
                }
            }
        } else {
            echo('请指定要优化的表！');
            //return redirect((string)url('admin/' . $this->model . '/export'));
        }
    }

    /**
     * 修复表
     * @param  String $tables 表名
     */
    public function repair(Request $request, $tables = null)
    {
        $tables = $request->param('tables');
        if ($tables) {
            if (is_array($tables)) {
                $tables = implode('`,`', $tables);
                $list = Db::query("REPAIR TABLE `{$tables}`");

                if ($request->isPost()) {
                    return json([
                        'code' => $list ? 1 : 0,
                        'status' => $list ? 'success' : 'error',
                        'info' => $list ? __('数据表修复完成！') : __('数据表修复出错请重试！'),
                        'referer' => (string)url('admin/' . $this->model . '/export')
                    ]);
                }
            } else {
                $list = Db::query("REPAIR TABLE `{$tables}`");

                if ($request->isPost()) {
                    return json([
                        'code' => $list ? 1 : 0,
                        'status' => $list ? 'success' : 'error',
                        'info' => $list ? __("数据表'{$tables}'修复完成！") : __("数据表'{$tables}'修复出错请重试！"),
                        'referer' => (string)url('admin/' . $this->model . '/export')
                    ]);
                }
            }
        } else {
            echo('请指定要修复的表！');
            //return redirect((string)url('admin/' . $this->model . '/export'));
        }
    }

    /**
     * 删除备份文件
     */
    public function delete(Request $request, $id = '')
    {
        if ($request->isPost()) {
            // 以时间命名的备份文件
            $time = $request->param('time');
            if ($time) {
                $name = '*' . $time . '-*.sql*';
                $path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
                array_map('unlink', glob($path));

                if ($request->isPost()) {
                    if (count(glob($path))) {
                        return json([
                            'code' => 0,
                            'status' => 'error',
                            'info' => '备份文件删除失败，请检查权限！',
                        ]);
                    } else {
                        return json([
                            'code' => 1,
                            'status' => 'success',
                            'info' => '备份文件删除成功！',
                        ]);
                    }
                }

                /*
                // 只能删除单一的文件
                $filepath = $request->param('filepath');
                $path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $filepath;
                if (unlink($path)) {
                    return json([
                        'code' => 1,
                        'status' => 'success',
                        'info' => '备份文件删除成功！',
                    ]);
                } else {
                    return json([
                        'code' => 0,
                        'status' => 'error',
                        'info' => '备份文件删除失败，请检查权限！',
                    ]);
                }
                */
            } else {
                exit('参数错误！');
            }
        } else {
            return $this->error('请求方式错误!');
        }
    }

    /**
     * 下载备份文件
     */
    public function download(Request $request)
    {
        $file = $request->param('filepath');
        if ($file) {
            $filePath = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $file;
            if (!file_exists($filePath)) {
                exit('该文件不存在，可能是被删除');
            }
            $filename = basename($filePath);
            header('pragma:public');
            header('Cache-Control:max-age=0'); // 禁止缓存
            header('Content-type: application/octet-stream');
            header('Content-Length: ' . filesize($filePath));
            header('Content-Disposition: attachment; filename="' . $filename . '"');

            readfile($filePath);
        } else {
            exit('参数错误！');
        }
    }

    /**
     * 备份数据库
     */
    public function exportPost(Request $request, $tables = null, $id = null, $start = null)
    {
        $param = $request->param();
        $tables = $param['tables'] ?? [];
        $id = $param['id'] ?? null;
        $start = $param['start'] ?? null;

        if ($request->isPost() && !empty($tables) && is_array($tables)) { // 初始化
            // 读取备份配置
            $config = $this->config;

            // 检查备份目录是否可写 创建备份目录
            is_writeable($config['path']) || mkdir($config['path'], 0777, true);

            // 检查是否有正在执行的任务
            $lock = $config['path'] . 'backup.lock';
            if (is_file($lock)) {
                return json([
                    'code' => 0,
                    'status' => 'error',
                    'info' => '检测到有一个备份任务正在执行，请稍后再试！',
                ]);
            } else {
                // 创建锁文件
                file_put_contents($lock, time());
            }

            //Session::set('backup_config', $config);
            session('backup_config', $config);

            // 生成备份文件信息
            $file = [
                //'name' => date('Ymd-His', time()),
                'name' => (count($tables) > 1) ? 'multi_tables-' . date('YmdHis', time()) : $tables[0] . '-' . date('YmdHis', time()),
                'part' => 1
            ];
            session('backup_file', $file);

            // 缓存要备份的表
            session('backup_tables', $tables);

            // 创建备份文件
            $dbObj = new Databases($file, $config);
            if ($dbObj->create() !== false) {
                $tab = array('id' => 0, 'start' => 0);

                return json([
                    'code' => 1,
                    'status' => 'success',
                    'info' => '初始化成功！',
                    'tables' => $tables,
                    'tab' => $tab
                ]);
            } else {
                return json([
                    'code' => 0,
                    'status' => 'error',
                    'info' => '初始化失败，备份文件创建失败！'
                ]);
            }
        } elseif ($request->isGet() && is_numeric($id) && is_numeric($start)) { // 备份数据
            $tables = session('backup_tables');
            //备份指定表
            $dbObj = new Databases(session('backup_file'), session('backup_config'));
            $start = $dbObj->backup($tables[$id], $start);
            if ($start === false) { // 出错
                return json([
                    'code' => 0,
                    'status' => 'error',
                    'info' => '备份出错！'
                ]);
            } elseif ($start === 0) { // 下一表
                if (isset($tables[++$id])) {
                    $tab = array('id' => $id, 'start' => 0);

                    return json([
                        'code' => 1,
                        'status' => 'success',
                        'info' => '备份完成！',
                        'tab' => $tab
                    ]);
                } else { // 备份完成，清空缓存
                    unlink(session('backup_config.path') . 'backup.lock');
                    //Session::delete('backup_tables');
                    session('backup_tables', null);
                    session('backup_file', null);
                    session('backup_config', null);

                    return json([
                        'code' => 1,
                        'status' => 'success',
                        'info' => '备份完成！'
                    ]);
                }
            } else {
                $tab = array('id' => $id, 'start' => $start[0]);
                $rate = floor(100 * ($start[0] / $start[1]));

                return json([
                    'code' => 1,
                    'status' => 'success',
                    'info' => "正在备份...({$rate}%)",
                    'tab' => $tab
                ]);
            }
        } else {
            // 出错
            return json([
                'code' => 0,
                'status' => 'error',
                'info' => '参数错误！'
            ]);
        }
    }

    /**
     * 还原数据库
     */
    public function importPost(Request $request, $part = null, $start = null)
    {
        $param = $request->param();
        //$filepath = $param['filepath'];
        $time = $param['time'];
        $part = $param['part'];
        $start = $param['start'];
        if (is_numeric($time) && is_null($part) && is_null($start)) { // 初始化
            // 获取备份文件信息
            $name = '*' . $time . '-*.sql*';
            $path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
            $files = glob($path);
            $list = array();
            foreach ($files as $name) {
                $basename = basename($name);
                // 表名前缀
                //$prefix = substr($basename, 0, stripos($basename,'-'));
                //$prefix = strstr($basename, '-', true);
                //$match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d'); // 没有表名前缀，只有时间戳
                $basename = substr(strstr($basename, '-'), 1);
                $match = sscanf($basename, '%4s%2s%2s%2s%2s%2s-%d');
                $gz = preg_match('/^db_\d{8,8}-\d{6,6}-\d+\.sql.gz$/', $basename);
                $list[$match[6]] = array($match[6], $name, $gz);
            }
            ksort($list);
            // 检测文件正确性
            $last = end($list);
            if (count($list) === $last[0]) {
                // 缓存备份列表
                session('backup_list', $list);

                return redirect((string)url('admin/database/importPost?part=1&start=0&time=' . $time));
            } else {
                return json([
                    'code' => 0,
                    'status' => 'error',
                    'info' => '备份文件可能已经损坏，请检查！'
                ]);
            }
        } elseif (is_numeric($part) && is_numeric($start)) {
            $list = session('backup_list');
            $dbObj = new Databases($list[$part], [
                'path' => realpath($this->config['path']) . DIRECTORY_SEPARATOR,
                'compress' => $list[$part][2]
            ]);
            $start = $dbObj->import($start);

            if ($start === false) {
                return json([
                    'code' => 0,
                    'status' => 'error',
                    'info' => '还原数据出错！'
                ]);
            } elseif ($start === 0) { // 下一卷
                if (isset($list[++$part])) {
                    $data = array('part' => $part, 'start' => 0);

                    return json([
                        'code' => 1,
                        'status' => 'success',
                        'info' => "正在还原...#{$part}",
                        'data' => $data
                    ]);
                } else {
                    session('backup_list', null);

                    return json([
                        'code' => 1,
                        'status' => 'success',
                        'info' => '还原完成！'
                    ]);
                }
            } else {
                $data = array('part' => $part, 'start' => $start[0]);
                if ($start[1]) {
                    $rate = floor(100 * ($start[0] / $start[1]));

                    return json([
                        'code' => 1,
                        'status' => 'success',
                        'info' => "正在还原...#{$part} ({$rate}%)",
                        'data' => $data
                    ]);
                } else {
                    $data['gz'] = 1;

                    return json([
                        'code' => 1,
                        'status' => 'success',
                        'info' => "正在还原...#{$part}",
                        'data' => $data
                    ]);
                }
            }
        } else {
            return json([
                'code' => 0,
                'status' => 'error',
                'info' => '参数错误！'
            ]);
        }
    }
}
